// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SynchronizationTest.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Test.Robotics.Runtime
{
    using System;
    using System.IO;
    using System.Reflection;
    using System.Runtime.Serialization;
    using System.Threading;
    using Microsoft.Robotics.Runtime;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// Validates that message synchronization works as expected.
    /// </summary>
    [TestClass]
    public class SynchronizationTest
    {
        /// <summary>
        /// Validates the SynchronizationAgent
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void Synchronization()
        {
            // inputs
            long[] input1Times = new long[] { 10, 20, 30, 40, 50 };
            long[] input2Times = new long[] { 11, 17, 21, 28, 38, 47, 51 };

            // publish the first set completely, then the second one
            int tolerance = 5;
            int[] order = new int[] { 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2 };
            long[,] expectedResults = new long[,] { { 10, 11 }, { 20, 17 }, { 30, 28 }, { 40, 38 }, { 50, 47 } };
            this.SynchronizationCheck(input1Times, input2Times, order, tolerance, expectedResults);

            // publish the second set completely, then the first one
            tolerance = 2;
            order = new int[] { 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1 };
            expectedResults = new long[,] { { 10, 11 }, { 20, 21 }, { 30, 28 }, { 40, 38 }, { 50, 51 } };
            this.SynchronizationCheck(input1Times, input2Times, order, tolerance, expectedResults);

            // alternate between sets
            tolerance = 5;
            order = new int[] { 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2 };
            expectedResults = new long[,] { { 10, 11 }, { 20, 17 }, { 30, 28 }, { 40, 38 }, { 50, 47 } };
            this.SynchronizationCheck(input1Times, input2Times, order, tolerance, expectedResults);

            // alternate between sets with a reduced tolerance
            tolerance = 1;
            order = new int[] { 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2 };
            expectedResults = new long[,] { { 10, 11 }, { 20, 21 }, { 50, 51 } };
            this.SynchronizationCheck(input1Times, input2Times, order, tolerance, expectedResults);

            // publish the first set completely, then the second one with reduced tolerance
            tolerance = 1;
            order = new int[] { 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2 };
            expectedResults = new long[,] { { 10, 11 }, { 20, 21 }, { 50, 51 } };
            this.SynchronizationCheck(input1Times, input2Times, order, tolerance, expectedResults);
        }

        /// <summary>
        /// Validates that the SynchronizationAgent works as expected.
        /// </summary>
        /// <param name="input1Times">The set of originating times for the messages on the first subscription.</param>
        /// <param name="input2Times">The set of originating times for the messages on the second subscription.</param>
        /// <param name="order">The order in which messages should be published.</param>
        /// <param name="tolerance">The sync tolerance, in ms.</param>
        /// <param name="expectedResults">The pairs of expected results.</param>
        private void SynchronizationCheck(long[] input1Times, long[] input2Times, int[] order, int tolerance, long[,] expectedResults)
        {
            int expectedResultCount = expectedResults.GetUpperBound(0) + 1;

            // wire up an agent pipeline for the SynchronizationAgent 
            AgentLocator locator = new AgentLocator();
            MessageInjectorAgent<SimpleAgentMessage<int>> input1 = new MessageInjectorAgent<SimpleAgentMessage<int>>("one");
            locator.Register(input1);

            MessageInjectorAgent<SimpleAgentMessage<double>> input2 = new MessageInjectorAgent<SimpleAgentMessage<double>>("two");
            locator.Register(input2);

            SynchronizationAgent<SimpleAgentMessage<int>, SimpleAgentMessage<double>> sync
                = new SynchronizationAgent<SimpleAgentMessage<int>, SimpleAgentMessage<double>>("sync", "one", "two", tolerance);
            locator.Register(sync);

            MessageCollectorAgent<MessageTuple<SimpleAgentMessage<int>, SimpleAgentMessage<double>>> collector
                = new MessageCollectorAgent<MessageTuple<SimpleAgentMessage<int>, SimpleAgentMessage<double>>>("collector", sync, expectedResultCount);
            IAgentHost collectorHost = locator.Register(collector);
            collectorHost.Activate();

            // Now pump messages into the pipeline and verify the results
            int i1 = 0;
            int i2 = 0;
            foreach (int inputIndex in order)
            {
                if (inputIndex == 1)
                {
                    long originatingTime = input1Times[i1];
                    SimpleAgentMessage<int> msg = new SimpleAgentMessage<int>((int)originatingTime, originatingTime);
                    input1.Publish(msg);
                    i1++;
                }
                else
                {
                    long originatingTime = input2Times[i2];
                    SimpleAgentMessage<double> msg = new SimpleAgentMessage<double>((int)originatingTime, originatingTime);
                    input2.Publish(msg);
                    i2++;
                }
            }

            if (!collector.Wait(1000))
            {
                throw new Exception(string.Format("Did not receive the expected number of messages {0}. Received {1}.", expectedResultCount, collector.Results.Count));
            }

            int i = 0;
            foreach (MessageTuple<SimpleAgentMessage<int>, SimpleAgentMessage<double>> msg in collector.Results)
            {
                Assert.AreEqual(expectedResults[i, 0], msg.Message1.OriginatingTime);
                Assert.AreEqual(expectedResults[i, 1], msg.Message2.OriginatingTime);
                i++;
            }

            collectorHost.Deactivate();
        }
    }
}
